
package cn.tannn.captcha.sdk.util;


import java.lang.reflect.Array;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Random;
import java.util.function.Supplier;

/**
 * @see org.apache.commons.lang3.RandomUtils
 */
public class RandomUtils {

    private static RandomUtils SECURE = new RandomUtils(SecureRandom::new);
    private final Supplier<Random> random;
    private RandomUtils(final Supplier<Random> random) {
        this.random = random;
    }

    Random random() {
        return random.get();
    }


    /**
     * Generates a random integer within the specified range.
     *
     * @param startInclusive the smallest value that can be returned, must be non-negative
     * @param endExclusive   the upper bound (not included)
     * @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is
     *                                  negative
     * @return the random integer
     */
    public static int nextInt(final int startInclusive, final int endExclusive) {
        return secure().randomInt(startInclusive, endExclusive);
    }

    /**
     * Gets the singleton instance based on {@link SecureRandom#SecureRandom()} which uses an algorithms/providers
     * specified in the {@code securerandom.strongAlgorithms} {@link Security} property.
     * <p>
     * The method {@link SecureRandom#SecureRandom()} is called on-demand.
     * </p>
     *
     * @return the singleton instance based on {@link SecureRandom#SecureRandom()}.
     * @see SecureRandom#SecureRandom()
     * @since 3.16.0
     */
    public static RandomUtils secure() {
        return SECURE;
    }


    /**
     * Generates a random integer within the specified range.
     *
     * @param startInclusive the smallest value that can be returned, must be non-negative
     * @param endExclusive   the upper bound (not included)
     * @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is
     *                                  negative
     * @return the random integer
     * @since 3.16.0
     */
    public int randomInt(final int startInclusive, final int endExclusive) {
        isTrue(endExclusive >= startInclusive, "Start value must be smaller or equal to end value.");
        isTrue(startInclusive >= 0, "Both range values must be non-negative.");
        if (startInclusive == endExclusive) {
            return startInclusive;
        }
        return startInclusive + random().nextInt(endExclusive - startInclusive);
    }


    public static void isTrue(final boolean expression, final String message, final Object... values) {
        if (!expression) {
            throw new IllegalArgumentException(getMessage(message, values));
        }
    }

    /**
     * Gets the message using {@link String#format(String, Object...) String.format(message, values)}
     * if the values are not empty, otherwise return the message unformatted.
     * This method exists to allow validation methods declaring a String message and varargs parameters
     * to be used without any message parameters when the message contains special characters,
     * e.g. {@code Validate.isTrue(false, "%Failed%")}.
     *
     * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
     * @param values the optional values for the formatted message
     * @return formatted message using {@link String#format(String, Object...) String.format(message, values)}
     * if the values are not empty, otherwise return the unformatted message.
     */
    private static String getMessage(final String message, final Object... values) {
        return isEmpty(values) ? message : String.format(message, values);
    }


    /**
     * Checks if an array of Objects is empty or {@code null}.
     *
     * @param array  the array to test
     * @return {@code true} if the array is empty or {@code null}
     * @since 2.1
     */
    public static boolean isEmpty(final Object[] array) {
        return isArrayEmpty(array);
    }

    /**
     * Checks if an array is empty or {@code null}.
     *
     * @param array the array to test
     * @return {@code true} if the array is empty or {@code null}
     */
    private static boolean isArrayEmpty(final Object array) {
        return getLength(array) == 0;
    }

    /**
     * Returns the length of the specified array.
     * This method can deal with {@link Object} arrays and with primitive arrays.
     * <p>
     * If the input array is {@code null}, {@code 0} is returned.
     * </p>
     * <pre>
     * ArrayUtils.getLength(null)            = 0
     * ArrayUtils.getLength([])              = 0
     * ArrayUtils.getLength([null])          = 1
     * ArrayUtils.getLength([true, false])   = 2
     * ArrayUtils.getLength([1, 2, 3])       = 3
     * ArrayUtils.getLength(["a", "b", "c"]) = 3
     * </pre>
     *
     * @param array  the array to retrieve the length from, may be null
     * @return The length of the array, or {@code 0} if the array is {@code null}
     * @throws IllegalArgumentException if the object argument is not an array.
     * @since 2.1
     */
    public static int getLength(final Object array) {
        return array != null ? Array.getLength(array) : 0;
    }
}
